import path from 'path';
import fs from 'fs';
import ts from 'typescript';
import colors from 'ansi-colors';
import crypto from 'crypto';

export function loopEach(input: string, callback: (path: string) => void) {
  if (!fs.existsSync(input)) return;
  const stats = fs.statSync(input);
  if (stats.isDirectory()) {
    const dir = fs.readdirSync(input);
    for (let i = 0; i < dir.length; i += 1) {
      const curPath = path.resolve(input, dir[i]);
      const curStats = fs.statSync(curPath);
      if (curStats.isDirectory()) {
        loopEach(curPath, callback);
      } else if (curStats.isFile()) {
        callback(curPath);
      }
    }
  } else {
    callback(input);
  }
}

export function getProjectDeclareFile(url: string) {
  if (!fs.existsSync(url)) return [];
  const dir = fs.readdirSync(url);
  const result: string[] = [];
  for (let i = 0; i < dir.length; i += 1) {
    if (!/node_modules/.test(dir[i])) {
      loopEach(`${url}/${dir[i]}`, (itemUrl) => {
        if (itemUrl.endsWith('.d.ts')) {
          result.push(itemUrl);
        }
      });
    }
  }
  return result;
}

export function replaceString(origin: string, start: number, end: number, replacer: string) {
  const left = origin.slice(0, start);
  const right = origin.slice(end);
  return left + replacer + right;
}

interface Depend {
  source: string;
  start: number;
  end: number;
}

function visitor(node: ts.Node, cb: (node: ts.Node) => void) {
  ts.forEachChild(node, (current) => {
    cb(current);
    visitor(current, cb);
  });
}

export function getAllDependencies(fileName: string, code: string) {
  const result: Depend[] = [];
  const sourcefile = ts.createSourceFile(fileName, code, ts.ScriptTarget.ESNext);

  visitor(sourcefile, (node) => {
    if (node.kind === ts.SyntaxKind.ImportDeclaration) {
      const { moduleSpecifier } = node as ts.ImportDeclaration;
      if (moduleSpecifier.kind === ts.SyntaxKind.StringLiteral) {
        result.push({
          source: (moduleSpecifier as ts.StringLiteral).text,
          start: moduleSpecifier.pos,
          end: moduleSpecifier.end,
        });
      }
    }
    if (node.kind === ts.SyntaxKind.ImportType) {
      const { argument } = node as ts.ImportTypeNode;
      if (argument.kind === ts.SyntaxKind.LiteralType) {
        const { literal } = argument as ts.LiteralTypeNode;
        if (literal.kind === ts.SyntaxKind.StringLiteral) {
          result.push({
            source: (literal as ts.StringLiteral).text,
            start: literal.pos,
            end: literal.end,
          });
        }
      }
    }
    if (node.kind === ts.SyntaxKind.ExportDeclaration) {
      const { moduleSpecifier } = node as ts.ExportDeclaration;
      if (moduleSpecifier && moduleSpecifier.kind === ts.SyntaxKind.StringLiteral) {
        result.push({
          source: (moduleSpecifier as ts.StringLiteral).text,
          start: moduleSpecifier.pos,
          end: moduleSpecifier.end,
        });
      }
    }
  });
  return result;
}

export function createDir(dir: string) {
  const str = path.resolve(dir);
  const arr = str.split(path.sep);
  let itemStr = arr.slice(0, 2).join(path.sep);
  if (!fs.existsSync(itemStr)) {
    fs.mkdirSync(itemStr);
  }
  for (let i = 2; i < arr.length; i += 1) {
    itemStr = path.resolve(itemStr, arr[i]);
    if (!fs.existsSync(itemStr)) {
      fs.mkdirSync(itemStr);
    }
  }
}

export function clearDir(dir: string) {
  if (!fs.existsSync(dir)) return;
  const stats = fs.statSync(dir);
  if (stats.isDirectory()) {
    const arr = fs.readdirSync(dir);
    for (let i = 0; i < arr.length; i += 1) {
      const curPath = path.resolve(dir, arr[i]);
      const curStats = fs.statSync(curPath);
      if (curStats.isDirectory()) {
        clearDir(curPath);
        fs.rmdirSync(curPath);
      } else if (curStats.isFile()) {
        fs.unlinkSync(curPath);
      }
    }
  }
}

export function findNPMDependencies(source: string, importer: string) {
  // arr is ['', 'user']
  // first is empty str
  const arr = path.dirname(importer).split(path.sep);
  while (arr.length > 2) {
    const nodeDir = path.resolve(arr.join(path.sep), 'node_modules', source);
    if (fs.existsSync(nodeDir)) {
      return nodeDir;
    }
    const typeDir = path.resolve(arr.join(path.sep), 'node_modules', '@types', source);
    if (fs.existsSync(typeDir)) {
      return nodeDir;
    }

    arr.pop();
  }
  return undefined;
}

export function logError(...msg: any[]) {
  console.log(...msg.map(colors.red));
}

export function logSuccess(...msg: any[]) {
  console.log(...msg.map(colors.green));
}

export function logWarning(...msg: any[]) {
  console.log(...msg.map(colors.yellow));
}

export function getFileName(filePath: string) {
  const arr = filePath.split(path.sep);
  const lastFile = arr[arr.length - 1];
  return lastFile.split('.')[0]
}


export function encodeFilePath(fileMap: Map<string, string>, filePath: string) {
  const fileName = getFileName(filePath);
  if (fileMap.has(filePath)) {
    return fileMap.get(filePath)!;
  }
  const values = [...fileMap.values()];
  const isInclude = values.includes(fileName);
  if (!isInclude) {
    fileMap.set(filePath, fileName);
    return fileName;
  }
  const md5 = crypto.createHash('md5');
  const str = md5.update(filePath).digest('hex') + '_' + fileName;
  fileMap.set(filePath, str);
  return str;
}
